<?php

namespace app\http\middleware;

use app\common\biz\BaseBiz;
use app\common\exception\ApiException;
use app\common\exception\BizException;
use Closure;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\JWT;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Exception;
use think\exception\HttpException;
use think\facade\Env;
use think\facade\Log;
use think\helper\Arr;
use think\Response;

class Authenticate
{
    /**
     * 用户授权信息认证
     * @param $request
     * @param Closure $next
     * @return Response
     */
    public function handle($request, Closure $next):Response
    {
        $jwt = $request->header('Authorization');
        try {
            JWT::$leeway = 60;//这个属性表示可以当前请求token的有效时间再延长60s
            list($type,$token) = explode(' ',$jwt);
            $decoded = JWT::decode($token, Env::get('secret.jwt','CT5'), ['HS256']);//HS256方式，这里要和签发的时候对应

            // 将授权凭证（一般内含有用户主键，可用此主键兑换用户信息）注入到请求中
            // 具体兑换逻辑请在对应模块中间件中实现（参考api/middleware/Account）
            $request->auth = (object)$decoded->data;//如果原始请求中有传递authorizion参数，那么会被覆盖，所以在定义这个的时候就记得要避免冲突了
        }
        catch(SignatureInvalidException $e) {
            //签名不正确
            throw new ApiException('[ 验签失败 ] 签名无效');
        }
        catch(BeforeValidException $e) {
            // 签名在某个时间点之后才能用
            throw new ApiException('[ 验签失败 ] 解析失败');
        }
        catch(ExpiredException $e) {
            // token过期
            throw new BizException('',LOGIN_OUT);
        }
        catch(Exception $e) {
            /**
             * 签名解析失败时会返回三中类型错误
             * ['BeforeValidException','ExpiredException','SignatureInvalidException']
             * 如果需要详细处理每种类型，捕获对应类型错误即可
             * 此处为统一处理，token错误，直接返回403错误码，不走后续逻辑（或者也可以走正常业务逻辑，视需求而定）
             */
            throw new ApiException('[ 验签失败 ] 解析失败');
            /**
             * 此处如果直接返回response类，则会导致后续注册的控制器中间件都无法执行，因为已经分支了管道
             * 核心问题，  没有执行$next($request)
             */
        }

        //签发者验证
        if(!$this->issDomainVerify($decoded->iss)) {
            throw new ApiException('[ 验签失败 ] 来源不可靠');
        }
        //接收者验证
        if(!$this->audDomainVerify($decoded->aud)) {
            throw new ApiException('[ 验签失败 ] 签名无法验收');
        }

        return $next($request);
    }

    /**
     * 签发者域名验证
     * @param string $host
     * @return bool
     */
    public function issDomainVerify(string $host):bool
    {
        $allow = [
            //默认有效签发者域名为auth+当前站点的根域名
            'auth.'.request()->rootDomain(),
        ];
        return in_array($host,$allow);
    }

    /**
     * 接收者域名验证
     * @param string $host
     * @return bool
     */
    public function audDomainVerify(string $host):bool
    {
        $arr1 = array_reverse(array_filter(explode('.',$host)),false);
        $arr2 = array_reverse(array_filter(explode('.',request()->subDomain().'.'.request()->rootDomain())),false);
        //将接收者的域名与当前站点的域名进行比较（$arr1,$arr2此处的处理还需理解）
        $diff = array_diff_assoc($arr1,$arr2);
        foreach ($diff as $val) {
            if($val == '*') continue;
            return false;
        }

        return true;
    }
}